package com.tfswx.jkgl.designer.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSON;
import com.tfswx.jkgl.core.bean.Csdy;
import com.tfswx.jkgl.core.bean.Jkdy;
import com.tfswx.jkgl.core.bean.JkglRuntimeData;
import com.tfswx.jkgl.core.yzfs.YzfsUtil;
import com.tfswx.jkgl.core.yzfs.base.YzfsBase;
import com.tfswx.jkgl.designer.bean.Clazz;
import com.tfswx.jkgl.designer.dto.DtoGetInputDTO;
import com.tfswx.jkgl.designer.dto.xmgl.XmxxGetInputDTO;
import com.tfswx.jkgl.designer.dto.xmgl.XmxxGetOutputDTO;
import com.tfswx.jkgl.designer.service.DtoService;
import com.tfswx.jkgl.designer.service.ExportService;
import com.tfswx.jkgl.designer.service.FsService;
import com.tfswx.jkgl.designer.service.XmglService;
import com.tfswx.jkgl.designer.util.ZipUtil;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author JayGoaler
 */
@Slf4j
@Service
public class DtoServiceImpl implements DtoService {
    @Resource
    private FsService fsService;
    @Resource
    private XmglService xmglService;

    @Resource
    private ExportService exportService;

    private static Template dtoTemp;
    private static Template apiTemp;
    private static Template serviveTemp;
    private static Template serviveImplTemp;
    private static Template valueRangeTemp;
    private static Map<String, YzfsBase> yzfsMap;

    static {
        yzfsMap = YzfsUtil.getYzfsMap();
        try {
            Configuration cfg = new Configuration(Configuration.VERSION_2_3_22);
            cfg.setDefaultEncoding("UTF-8");
            // ClassPathResource resource = new ClassPathResource("resource.properties");
            // InputStream is = resource.getInputStream();
            cfg.setClassForTemplateLoading(DtoServiceImpl.class, "/templates");
            cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
            dtoTemp = cfg.getTemplate("dtoTemp.ftl");
            apiTemp = cfg.getTemplate("apiTemp.ftl");
            serviveTemp = cfg.getTemplate("serviveTemp.ftl");
            serviveImplTemp = cfg.getTemplate("serviveImplTemp.ftl");
            valueRangeTemp = cfg.getTemplate("valueRangeTemp.ftl");
        } catch (IOException e) {
            log.error("模板读取错误", e);
        }
    }

    @SneakyThrows
    @Override
    public void createDtoTemplate(DtoGetInputDTO inputDTO) {
        // try {
        //     return getDto(inputDTO.getCspz(), inputDTO.getCjr(), DateUtil.now());
        // }catch (Exception e){
        //     throw new RuntimeException("传入参数存在暂不支持的数据类型！");
        // }
        XmxxGetInputDTO xmxxGetInputDTO = new XmxxGetInputDTO();
        xmxxGetInputDTO.setXmbm(inputDTO.getXmbm());
        XmxxGetOutputDTO xmxxGetOutputDTO = xmglService.getXmxx(xmxxGetInputDTO);
        if (xmxxGetOutputDTO == null) {
            throw new RuntimeException("没有查询导对应的项目");
        }
        String basePkg = xmxxGetOutputDTO.getBm();
        if (StringUtils.isBlank(basePkg)) {
            basePkg = "com.tfswx.jcyw." + xmxxGetOutputDTO.getXmbs().toLowerCase();
        }

        JkglRuntimeData jkglRuntimeData = exportService.getJkglRuntimeData(inputDTO.getXmbm());
        Map<String, Jkdy> jkdyList = jkglRuntimeData.getJkdyList();

        List<Jkdy> jkdies = new ArrayList<>();
        for (Map.Entry<String, Jkdy> stringJkdyEntry : jkdyList.entrySet()) {
            Jkdy value = stringJkdyEntry.getValue();
            jkdies.add(value);
            value.setMkbs(StringUtils.isNotBlank(value.getMkbs()) ? value.getMkbs() : "unkown");
        }
        File dir = new File("dtoTest", IdUtil.fastSimpleUUID());
        // dir = new File("src/main/java");

        try {
            // 创建基本目录
            File baseDir = new File(dir, basePkg.replace(".", File.separator));
            if (baseDir.exists()) {
                FileUtils.deleteDirectory(baseDir);
            }

            // 创建dto
            List<Clazz> dtoClazzs = createDto(jkdies, basePkg);
            List<Clazz> files = new ArrayList<>(dtoClazzs);

            // 创建api
            List<Clazz> apiClazzs = createApi(jkdies, basePkg);
            files.addAll(apiClazzs);

            // 创建service
            List<Clazz> serviceClazzs = createService(jkdies, basePkg);
            files.addAll(serviceClazzs);

            // 创建 validation
            Clazz valueRange = createValueRange(basePkg);
            files.add(valueRange);

            for (Clazz clazz : files) {
                // List<Clazz.Field> fields = clazz.getFields();
                // List<Clazz.Method> methods = clazz.getMethods();
                // List<Clazz> clazzes = clazz.getClazzes();
                // if (fields.isEmpty() && methods.isEmpty() && clazzes.isEmpty()) {
                //     continue;
                // }
                String pkgName = clazz.getPkgName();
                File javaFile = new File(dir, pkgName.replace(".", File.separator) + File.separator + clazz.getClassName() + ".java");
                if (!javaFile.getParentFile().exists()) {
                    javaFile.getParentFile().mkdirs();
                }
                Map<String, Object> root = new HashMap<>();
                root.put("clazz", clazz);
                try (Writer out = new OutputStreamWriter(new FileOutputStream(javaFile))) {
                    clazz.getTemplate().process(root, out);
                } catch (TemplateException | IOException e) {
                    e.printStackTrace();
                }
            }

            File zipFile = new File(dir.getParentFile(), xmxxGetOutputDTO.getXmmc() + "-" + DateUtil.format(new Date(), "yyyyMMddHHmmss") + ".zip");
            ZipUtil.compression(zipFile, dir, true);

            fsService.download(zipFile);
        } finally {
            FileUtils.deleteDirectory(dir);
        }
    }

    Pattern compile = Pattern.compile("[A-Z]");

    private String getNewName(String jkmc) {
        Matcher matcher = compile.matcher(jkmc);
        if (matcher.find()) {
            MatchResult matchResult = matcher.toMatchResult();
            int start = matchResult.start();
            if (start > 0) {
                String substring = jkmc.substring(0, start);
                String substring1 = jkmc.substring(start);
                jkmc = substring1 + StringUtils.capitalize(substring);
            }
        }
        jkmc = StringUtils.capitalize(jkmc);
        return jkmc;
    }

    private Clazz genDto(Jkdy jkdy, boolean sfrc, String basePkg) {

        String jkmc = jkdy.getJkmc();

        jkmc = getNewName(jkmc);

        String pkgName = basePkg + ".dto." + jkdy.getMkbs().toLowerCase();

        // inputDTO类
        Clazz clazz = new Clazz();
        clazz.setTemplate(dtoTemp);
        clazz.setPkgName(pkgName);
        clazz.setClassName(jkmc + (sfrc ? "InputDTO" : "OutputDTO"));
        clazz.addAnn("Data");
        clazz.addImort("lombok.*");
        clazz.addImort("io.swagger.annotations.*");
        clazz.addImort("java.util.*");
        clazz.addImort("javax.validation.*");
        clazz.addImort("javax.validation.constraints.*");
        clazz.addImort(basePkg + ".validation.ValueRange");

        String cssl = jkdy.getRcsl();
        String cspz = jkdy.getRcpz();
        if (!sfrc) {
            cspz = jkdy.getCcpz();
            cssl = jkdy.getCcsl();
        }
        if (csslIsBlank(cssl)) {
            return null;
        }
        Csdy csdy = JSON.parseObject(cspz, Csdy.class);

        gen(clazz, csdy, clazz);

        return clazz;
    }

    private void gen(Clazz clazz, Csdy csdy, Clazz rootClass) {
        List<Csdy> csdy1 = csdy.getCsdy();
        if (csdy1 == null) {
            return;
        }
        for (Csdy csdy2 : csdy1) {

            Clazz.Field field = new Clazz.Field();
            field.setName(csdy2.getCsbm());
            if ("List".equals(csdy2.getCslx())) {
                String jhlx = csdy2.getJhlx();
                if ("Map".equals(jhlx)) {
                    // 内部类
                    Clazz inputDto = new Clazz();
                    inputDto.setClassName(StringUtils.capitalize(csdy2.getCsbm()));
                    inputDto.addAnn("Data");

                    rootClass.addClazz(inputDto);
                    gen(inputDto, csdy2, rootClass);
                    field.setLx(csdy2.getCslx() + "<" + inputDto.getClassName() + ">");
                    field.addAnn("Valid");
                } else {
                    field.setLx(csdy2.getCslx() + "<" + jhlx + ">");
                }
            } else if ("Map".equals(csdy2.getCslx())) {
                // 内部类
                Clazz inputDto = new Clazz();
                inputDto.setClassName(StringUtils.capitalize(csdy2.getCsbm()));
                inputDto.addAnn("Data");

                rootClass.addClazz(inputDto);
                gen(inputDto, csdy2, rootClass);
                field.setLx(inputDto.getClassName());
                field.addAnn("Valid");
            } else {
                field.setLx(csdy2.getCslx());
            }
            String cssl = csdy2.getCssl() != null ? String.valueOf(csdy2.getCssl()) : "";
            List<Csdy.Yzfs> yzfs = csdy2.getYzfs();
            boolean required = false;
            for (Csdy.Yzfs yzf : yzfs) {
                YzfsBase yzfsBase = yzfsMap.get(StringUtils.uncapitalize(yzf.getYzlx()));
                String tsxx = yzf.getTsxx();
                if (StringUtils.isBlank(tsxx)) {
                    tsxx = csdy2.getCsbm() + "-" + yzfsBase.getMessage();
                }
                String ann = StringUtils.capitalize(yzf.getYzlx()) + "(" + "message = \"" + tsxx + "\"";
                if (StringUtils.isNotBlank(yzf.getYzz())) {
                    ann += ", value = " + yzfsBase.getYzzValue(yzf.getYzz());
                }
                if ("notBlank".equalsIgnoreCase(yzf.getYzlx()) || "notNull".equalsIgnoreCase(yzf.getYzlx())) {
                    required = true;
                }
                ann += ")";

                field.addAnn(ann);
            }
            String ann = "ApiModelProperty(";
            if (StringUtils.isNotBlank(csdy2.getCsmc())) {
                ann += "value = \"" + csdy2.getCsmc() + "\"";
            }
            if (StringUtils.isNotBlank(cssl)) {
                if (!ann.endsWith("(")) {
                    ann += ", ";
                }
                ann += "example = \"" + cssl.replace("\\", "\\\\").replace("\"", "\\\"") + "\"";
            }
            if (required) {
                if (!ann.endsWith("(")) {
                    ann += ", ";
                }
                ann += "required = true";
            }
            if (csdy2.getSfyc() != null && csdy2.getSfyc()) {
                if (!ann.endsWith("(")) {
                    ann += ", ";
                }
                ann += "hidden = true";
            }
            ann += ")";
            if (!ann.endsWith("()")) {
                field.addAnn(ann);
            }
            // clazz.addImort(servicePkg + "." + field.getLx());
            clazz.addField(field);
        }
    }

    // ***************************************
    private List<Clazz> createDto(List<Jkdy> jkdies, String basePkg) {
        List<Clazz> clazzList = new ArrayList<>();
        Map<String, List<Jkdy>> collect = jkdies.stream().collect(Collectors.groupingBy(Jkdy::getMkbs));
        for (Map.Entry<String, List<Jkdy>> stringJkdyEntry : collect.entrySet()) {
            List<Jkdy> value = stringJkdyEntry.getValue();
            for (Jkdy jkdy : value) {
                Clazz inputDto = genDto(jkdy, true, basePkg);
                if (inputDto != null) {
                    inputDto.setDesc(jkdy.getJkms() + "入参");
                    clazzList.add(inputDto);
                }
                Clazz outputDto = genDto(jkdy, false, basePkg);
                if (outputDto != null) {
                    outputDto.setDesc(jkdy.getJkms() + "出参");
                    clazzList.add(outputDto);
                }
            }
        }
        return clazzList;
    }

    private List<Clazz> createApi(List<Jkdy> jkdies, String basePkg) {
        List<Clazz> apiList = new ArrayList<>();
        Map<String, List<Jkdy>> collect = jkdies.stream().collect(Collectors.groupingBy(Jkdy::getMkbs));
        int j = 0;
        for (Map.Entry<String, List<Jkdy>> stringJkdyEntry : collect.entrySet()) {
            String mkbs = stringJkdyEntry.getKey();
            String upperMkbs = StringUtils.capitalize(mkbs);
            List<Jkdy> value = stringJkdyEntry.getValue();
            value.sort((a, b) -> (int) (a.getXh() - b.getXh()));
            Clazz api = new Clazz();
            api.setTemplate(apiTemp);
            api.setPkgName(basePkg + ".api");
            api.addImort(basePkg + ".dto." + mkbs + ".*");
            api.addImort(basePkg + ".query.*");
            api.addImort("io.swagger.annotations.*");
            api.addImort("org.springframework.web.bind.annotation.*");
            api.addImort("org.springframework.validation.annotation.Validated");
            api.addImort("com.github.xiaoymin.knife4j.annotations.*");
            api.addImort("javax.annotation.Resource");
            api.addImort("java.util.List");
            api.addImort(basePkg + ".service." + upperMkbs + "Service");
            j++;
            Jkdy item = value.get(0);
            api.addAnn("RestController");
            if (item != null) {
                api.addAnn("Api(tags = \"" + item.getMkmc() + "\")");
                api.setDesc(item.getMkms());
            } else {
                api.addAnn("Api(tags = \"\")");
            }
            api.addAnn("ApiSupport(order = " + (j * 10) + ")");
            api.addAnn("RequestMapping(\"" + mkbs + "\")");
            api.setClassName(upperMkbs + "Controller");

            Clazz.Field serviceField = new Clazz.Field();
            serviceField.addAnn("Resource");
            serviceField.setLx(upperMkbs + "Service");
            serviceField.setName(mkbs + "Service");
            api.addField(serviceField);

            apiList.add(api);
            int i = 0;
            for (Jkdy jkdy : value) {
                if ("Y".equals(jkdy.getSfnbjk())) {
                    continue;
                }
                i++;
                Clazz.Method method = new Clazz.Method();
                String jkmc = jkdy.getJkmc();
                String qqfs = jkdy.getQqfs();
                String rcsl = jkdy.getRcsl();
                String ccsl = jkdy.getCcsl();
                method.setName(jkmc);
                String newName = getNewName(jkmc);
                method.addAnn("ApiOperation(\"" + jkdy.getJkms() + "\")");
                method.addAnn("ApiOperationSupport(order = " + (i * 10) + ")");
                if ("post".equalsIgnoreCase(qqfs)) {
                    method.addAnn("PostMapping(\"" + jkmc + "\")");
                } else {
                    method.addAnn("GetMapping(\"" + jkmc + "\")");
                }
                if (!csslIsBlank(rcsl)) {
                    if ("post".equalsIgnoreCase(qqfs)) {
                        method.addParamAnn("RequestBody");
                    }
                    String jklx = jkdy.getJklx();
                    if ("4".equals(jklx)) {
                        // 分页查询
                        method.setParamType("PageParam<" + newName + "InputDTO>");
                    } else if (rcsl.startsWith("[")) {
                        method.setParamType("List<" + newName + "InputDTO>");
                    } else {
                        method.setParamType(newName + "InputDTO");
                    }
                    method.addParamAnn("Validated");
                }
                if (!csslIsBlank(ccsl)) {
                    String jklx = jkdy.getJklx();
                    if ("4".equals(jklx)) {
                        method.setReturnType("PageResult<" + newName + "OutputDTO>");
                    } else if (ccsl.startsWith("[")) {
                        method.setReturnType("List<" + newName + "OutputDTO>");
                    } else {
                        method.setReturnType(newName + "OutputDTO");
                    }
                }
                api.addMethod(method);
            }
        }
        return apiList;
    }

    private boolean csslIsBlank(String cssl) {
        return "\"\"".equals(cssl);
    }

    private List<Clazz> createService(List<Jkdy> jkdies, String basePkg) {
        List<Clazz> clazzList = new ArrayList<>();
        Map<String, List<Jkdy>> collect = jkdies.stream().collect(Collectors.groupingBy(Jkdy::getMkbs));
        for (Map.Entry<String, List<Jkdy>> stringJkdyEntry : collect.entrySet()) {
            String mkbs = stringJkdyEntry.getKey();
            List<Jkdy> value = stringJkdyEntry.getValue();
            value.sort((a, b) -> (int) (a.getXh() - b.getXh()));
            Clazz servive = new Clazz();
            servive.setTemplate(serviveTemp);
            servive.setPkgName(basePkg + ".service");
            servive.addImort(basePkg + ".dto." + mkbs + ".*");
            servive.addImort("java.util.List");
            servive.addImort(basePkg + ".query.*");

            Jkdy jkdy1 = value.get(0);
            if (jkdy1 != null) {
                servive.setDesc(jkdy1.getMkms());
            }

            servive.setClassName(StringUtils.capitalize(mkbs) + "Service");

            clazzList.add(servive);

            Clazz serviveImpl = new Clazz();
            serviveImpl.setTemplate(serviveImplTemp);
            serviveImpl.setPkgName(basePkg + ".service.impl");
            serviveImpl.addImort(basePkg + ".dto." + mkbs + ".*");
            serviveImpl.addImort(basePkg + ".service." + servive.getClassName());
            serviveImpl.addImort("org.springframework.stereotype.Service");
            serviveImpl.addImort("java.util.List");
            serviveImpl.addImort(basePkg + ".query.*");
            serviveImpl.addImort(basePkg + ".util.PageUtil");

            serviveImpl.addAnn("Service");
            serviveImpl.setClassName(StringUtils.capitalize(mkbs) + "ServiceImpl");
            serviveImpl.addImpl(servive.getClassName());
            clazzList.add(serviveImpl);
            for (Jkdy jkdy : value) {
                Clazz.Method method = new Clazz.Method();
                String jkmc = jkdy.getJkmc();
                method.setName(jkmc);
                String newName = getNewName(jkmc);
                method.addAnn("Override");
                String rcsl = jkdy.getRcsl();
                String ccsl = jkdy.getCcsl();
                String ywljms = jkdy.getYwljms();
                String jklx = jkdy.getJklx();
                if (ywljms == null) {
                    ywljms = "";
                }
                List<String> l = new ArrayList<>(Arrays.asList(ywljms.split("\n")));
                if ("4".equals(jklx)) {
                    l.add("分页使用,doSelectPage第二个参数中请勿执行多个xxxMaper方法，分页仅对第一个xxxMaper方法生效");
                    l.add("PageUtil.doSelectPage(inputDTO, () -> xxxMaper.xxx());");
                }
                method.setYwmsList(l);
                if (!csslIsBlank(rcsl)) {
                    if ("4".equals(jklx)) {
                        method.setParamType("PageParam<" + newName + "InputDTO>");
                    } else if (rcsl.startsWith("[")) {
                        method.setParamType("List<" + newName + "InputDTO>");
                    } else {
                        method.setParamType(newName + "InputDTO");
                    }
                }
                if (!csslIsBlank(ccsl)) {
                    if ("4".equals(jklx)) {
                        method.setReturnType("PageResult<" + newName + "OutputDTO>");
                    } else if (ccsl.startsWith("[")) {
                        method.setReturnType("List<" + newName + "OutputDTO>");
                    } else {
                        method.setReturnType(newName + "OutputDTO");
                    }
                }
                servive.addMethod(method);
                serviveImpl.addMethod(method);
            }
        }
        return clazzList;
    }

    // 创建ValueRange
    private Clazz createValueRange(String basePkg) {
        String pkgName = basePkg + ".validation";
        Clazz clazz = new Clazz();
        clazz.setTemplate(valueRangeTemp);
        clazz.setPkgName(pkgName);
        clazz.setClassName("ValueRange");
        return clazz;
    }
}
